#include "hashtable.h"

#include <stdint.h>

#include "slice.h"

typedef struct Entry {
  void* key;
  void* val;
  struct Entry* next;
} Entry;

static Entry* EntryCreate(void* key, void* val) {
  Entry* entry = (Entry *)malloc(sizeof(Entry));
  entry->key = key;
  entry->val = val;
  entry->next = NULL;
  return entry;
}

static void EntryDestroy(Entry* entry, void (*freekey)(void*), void (*freeval)(void*)) {
  if (entry) {
    if (freekey) freekey(entry->key);
    if (freeval) freeval(entry->val);
    free(entry);
  }
}

typedef struct Slot {
  Entry* head;
} Slot;

static Slot* SlotCreate() {
  Slot* slot = (Slot *)malloc(sizeof(Slot));
  slot->head = NULL;
  return slot; 
}

static void SlotDestroy(Slot* slot, void (*freekey)(void*), void (*freeval)(void*)) {
  if (slot) {
    Entry** entry = &slot->head;
    while (*entry) {
      Entry* t = *entry;
      entry = &t->next;
      EntryDestroy(t, freekey, freeval);
    }
    free(slot);
  }
}

static Entry* SlotFind(Slot* slot, const void* key, int (*equals)(const void*, const void*)) {
  Entry* entry = slot->head;
  while (entry) {
    if (equals(key, entry->key)) return entry;
    entry = entry->next;
  }
  return NULL;
}

static int SlotInsert(Slot* slot, void* key, void* val, void (*freeval)(void*), int (*equals)(const void*, const void*)) {
  Entry* entry = SlotFind(slot, key, equals);
  if (entry == NULL) {
    entry = EntryCreate(key, val);
    entry->next = slot->head;
    slot->head = entry;
    return 1;
  }
  freeval(entry->val);
  entry->val = val;
  return 0;
}

static void* SlotRemove(Slot* slot, const void* key, void (*freekey)(void*), int (*equals)(const void*, const void*)) {
  Entry** entry = &slot->head;
  while (*entry) {
    Entry* t = *entry;
    if (equals(key, t->key)) {
      void* val = t->val;
      *entry = t->next;
      EntryDestroy(t, freekey, NULL);
      return val;
    }
  }
  return NULL;
}

static int IntEquals(const void* a, const void* b) {
  return a == b;
}

static int StringEquals(const void* a, const void* b) {
  const Slice aa = (const Slice)a;
  const Slice bb = (const Slice)b;
  return SliceCompare(aa, bb) == 0;
}

static uint32_t IntHash(const void* a) {
  return (uint32_t)(intptr_t)a;
}

static uint32_t StringHash(const void* a) {
  const Slice aa = (const Slice)a;
  return SliceHash(aa);
}

HashTable* HashTableCreate(int capacity, uint32_t (*hash)(const void*), int (*equals)(const void*, const void*), void (*freekey)(void*), void (*freeval)(void*)) {
  HashTable* table = (HashTable *)malloc(sizeof(HashTable) + sizeof(struct Slot*) * capacity);  
  memset(table, 0, sizeof(HashTable) + sizeof(struct Slot*) * capacity);
  pthread_rwlock_init(&table->lock, NULL);
  table->length = capacity;
  table->hash = hash;
  table->equals = equals;
  table->freekey = freekey;
  table->freeval = freeval;
  return table;
}

HashTable* IntKeyHashTableCreate(int capacity, void (*freeval)(void*)) {
  return HashTableCreate(capacity, IntHash, IntEquals, NULL, freeval); 
}

HashTable* StringKeyHashTableCreate(int capacity, void (*freeval)(void*)) {
  return HashTableCreate(capacity, StringHash, StringEquals, SliceDestroy, freeval);
}

void HashTableDestroy(void* data) {
  if (data) {
    HashTable* table = (HashTable *)data;
    for (int i = 0; i < table->length; i++) {
      SlotDestroy(table->slots[i], table->freekey, table->freeval); 
    }
    pthread_rwlock_destroy(&table->lock);
    free(table);
  }
}

void HashTableSafeClear(HashTable* table) {
  pthread_rwlock_wrlock(&table->lock);
  for (int i = 0; i < table->length; i++) {
    SlotDestroy(table->slots[i], table->freekey, table->freeval); 
    table->slots[i] = NULL;
  }
  pthread_rwlock_unlock(&table->lock);
}

const void* HashTableFind(HashTable* table, const void* key) {
  uint32_t hash = table->hash(key);
  Slot* slot = table->slots[hash % table->length];
  if (slot == NULL) return NULL;
  const Entry* entry = SlotFind(slot, key, table->equals);
  return entry ? entry->val : NULL;
}

const void* HashTableSafeFind(HashTable* table, const void* key) {
  pthread_rwlock_rdlock(&table->lock);
  const void* t = HashTableFind(table, key);
  pthread_rwlock_unlock(&table->lock);
  return t;
}

void* HashTableRemove(HashTable* table, const void* key) {
  uint32_t hash = table->hash(key);
  Slot* slot = table->slots[hash % table->length];
  if (slot == NULL) return NULL;
  return SlotRemove(table->slots[hash % table->length], key, table->freekey, table->equals); 
}

void* HashTableSafeRemove(HashTable* table, const void* key) {
  pthread_rwlock_wrlock(&table->lock);
  void* t = HashTableRemove(table, key);
  pthread_rwlock_unlock(&table->lock);
  return t;
}

int HashTableInsert(HashTable* table, void* key, void* val) {
  uint32_t hash = table->hash(key);
  Slot** slot = table->slots + hash % table->length;
  if (*slot == NULL) *slot = SlotCreate();
  int n = SlotInsert(table->slots[hash % table->length], key, val, table->freeval, table->equals);
  if (n == 0) table->freekey(key);
  return n;
}

int HashTableSafeInsert(HashTable* table, void* key, void* val) {
  pthread_rwlock_wrlock(&table->lock);
  int t = HashTableInsert(table, key, val);
  pthread_rwlock_unlock(&table->lock);
  return t;
}

HashTableIterator HashTableGetIterator(HashTable* table) {
  HashTableIterator itr;
  itr.table = table;
  itr.idx = 0;
  itr.next = NULL;
  for (; itr.idx < table->length; itr.idx++) {
    if (table->slots[itr.idx] && table->slots[itr.idx]->head) {
      itr.next = (void **)&table->slots[itr.idx]->head;
      return itr;
    }
  }
  return itr;
}

void* HashTableRemoveNext(HashTableIterator* itr) {
  void* val = NULL;
  if (itr->next == NULL || *itr->next == NULL) return val;
  Entry* entry = (Entry *)*itr->next;
  if (entry->next) {
    *(itr->next) = entry->next;
  } else {
    *(itr->next) = NULL;
    for (; itr->idx < itr->table->length; itr->idx++) {
      if (itr->table->slots[itr->idx] && itr->table->slots[itr->idx]->head) {
        itr->next = (void **)&itr->table->slots[itr->idx]->head;
      } 
    }
  }
  val = entry->val;
  EntryDestroy(entry, itr->table->freekey, NULL);
  return val;
}

void* HashTableNext(HashTableIterator* itr) {
  void* val = NULL;
  if (itr->next == NULL || *itr->next == NULL) return val;
  Entry* entry = (Entry *)*itr->next;
  if (entry->next) {
    itr->next = (void **)&entry->next;
  } else {
    itr->next = NULL;
    itr->idx++;
    for (; itr->idx < itr->table->length; itr->idx++) {
      if (itr->table->slots[itr->idx] && itr->table->slots[itr->idx]->head) {
        itr->next = (void **)&itr->table->slots[itr->idx]->head;
        break;
      }
    }
  }
  val = entry->val;
  return val;
}


